﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Security.Cryptography;

namespace CompareDirectory
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            label2.Text = "";
            label3.Text = "";
        }

        /* class FileCmp
         {
             public long Len;
             public string MD5 = "";
             public string Path;
             public string LocalPath;
             public string Name;
             public bool IsDirectory;

             public FileCmp(string FilePath, string HomePath)
             {
                 Path = FilePath;
                 LocalPath = FilePath.Replace(HomePath, "");
                 FileInfo fi = new FileInfo(FilePath);
                 Len = fi.Length;
                 Name = fi.Name;
                 IsDirectory = string.IsNullOrEmpty(fi.Extension);
             }
         }
         */

        public class FileCmp
        {
            public long Len;
            public DateTime ChangeDate;
            public string MD5 = "";
            public string Path;
            public string LocalPath;
            public string LOCAL_PATH;
            public string Name;
            public bool IsDirectory;
            public string LocalParentDir;
            public string LOCAL_PARENT_DIR;
            public string HomePath;

            /// <summary>расширение файла (без точки)</summary>
            public string Ext
            {
                get
                {
                    string ret = System.IO.Path.GetExtension(Name);
                    if (ret != "")
                        return ret.Substring(1);
                    return "";
                }
            }

            public FileCmp(string FilePath, string HomePath, bool IsDirectory)
            {
                Path = FilePath;
                this.HomePath = HomePath;

                FileInfo fi = new FileInfo(FilePath);
                ChangeDate = IsDirectory ? fi.CreationTimeUtc : fi.LastWriteTimeUtc;
                this.IsDirectory = IsDirectory;
                if (!IsDirectory) Len = fi.Length;
                Name = fi.Name;

                LocalPath = Path.Replace(HomePath, "");
                LOCAL_PATH = LocalPath.ToUpper();
                LocalParentDir = LocalPath.Substring(0, LocalPath.Length - Name.Length);
                LOCAL_PARENT_DIR = LocalParentDir.ToUpper();
            }

            public FileCmp(BinaryReader Br)
            {
                Br.ReadByte();
                Len = Br.ReadInt64();
                ChangeDate = new DateTime(Br.ReadInt64());
                MD5 = Br.ReadString();
                Path = Br.ReadString();
                HomePath = Br.ReadString();
                Name = Br.ReadString();
                IsDirectory = Br.ReadBoolean();

                LocalPath = Path.Replace(HomePath, "");
                LOCAL_PATH = LocalPath.ToUpper();
                LocalParentDir = LocalPath.Substring(0, LocalPath.Length - Name.Length);
                LOCAL_PARENT_DIR = LocalParentDir.ToUpper();
            }

            /// <summary>Локальные пути совпадают</summary>
            public bool LocalPathEqual(FileCmp other, bool MatchCase) { return MatchCase ? LocalPath == other.LocalPath : LOCAL_PATH == other.LOCAL_PATH; }

            public void Save(BinaryWriter Bw)
            {
                Bw.Write((byte)0);
                Bw.Write(Len);
                Bw.Write(ChangeDate.Ticks);
                Bw.Write(MD5);
                Bw.Write(Path);
                Bw.Write(HomePath);
                Bw.Write(Name);
                Bw.Write(IsDirectory);
            }

            public override string ToString()
            {
                return LocalPath;
            }

        }

        List<FileCmp> Only1 = new List<FileCmp>();
        List<FileCmp> Only2 = new List<FileCmp>();
        List<FileCmp> Diff = new List<FileCmp>();

        public class Snapshot
        {
            public List<FileCmp> DList;
            public List<FileCmp> FList;
            public string HomePath = "";

            /// <summary>некоторые каталоги остались пустыми, так как нет прав на чтение</summary>
            public bool UnauthorizedAccessSomeDirs = false;

            public Snapshot()
            {
                DList = new List<FileCmp>();
                FList = new List<FileCmp>();
            }

            public Snapshot(BinaryReader Br)
            {
                Load(Br);
            }

            void Load(BinaryReader Br)
            {
                DList = new List<FileCmp>();
                FList = new List<FileCmp>();
                Br.ReadByte();
                HomePath = Br.ReadString();
                int len = Br.ReadInt32();
                for (int i = 0; i < len; i++) DList.Add(new FileCmp(Br));
                len = Br.ReadInt32();
                for (int i = 0; i < len; i++) FList.Add(new FileCmp(Br));
                UnauthorizedAccessSomeDirs = Br.ReadBoolean();
            }

            public void Save(BinaryWriter Bw)
            {
                Bw.Write((byte)0);
                Bw.Write(HomePath);
                Bw.Write(DList.Count);
                foreach (FileCmp d in DList) d.Save(Bw);
                Bw.Write(FList.Count);
                foreach (FileCmp f in FList) f.Save(Bw);
                Bw.Write(UnauthorizedAccessSomeDirs);
            }

            public void Save(string fPath)
            {
                using (MemoryStream ms = new MemoryStream())
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    Save(bw);
                    File.WriteAllBytes(fPath, ms.ToArray());
                }
            }

            /// <summary>Конструктор. Получение снимка каталога</summary>
            /// <param name="HomePath">каталог для снимка</param>
            public Snapshot(string path, bool loadFromFile)
            {
                DList = new List<FileCmp>();
                FList = new List<FileCmp>();

                if (loadFromFile)
                {
                    using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(path)))
                    using (BinaryReader br = new BinaryReader(ms))
                    {
                        Load(br);
                    }
                }
                else
                {
                    this.HomePath = path;
                    if (this.HomePath[this.HomePath.Length - 1] != '\\') this.HomePath += "\\";
                    FillDirectoriesAndFiles();
                }
            }


            void FillDirectoriesAndFiles()
            {
                string[] dl = Directory.GetDirectories(this.HomePath);
                string[] fl = Directory.GetFiles(this.HomePath);
                foreach (string d in dl) { DList.Add(new FileCmp(d, this.HomePath, true)); }
                foreach (string f in fl) { FList.Add(new FileCmp(f, this.HomePath, false)); }

                List<FileCmp> appenedDirList = DList;
                do
                {
                    List<FileCmp> dirList = appenedDirList;
                    appenedDirList = new List<FileCmp>();
                    foreach (FileCmp dir in dirList)
                    {
                        try
                        {
                            string[] dlCh = Directory.GetDirectories(dir.Path);
                            string[] flCh = Directory.GetFiles(dir.Path);
                            foreach (string d in dlCh) appenedDirList.Add(new FileCmp(d, this.HomePath, true));
                            foreach (string f in flCh) FList.Add(new FileCmp(f, this.HomePath, false));
                        }
                        catch (Exception ex)
                        {
                            if (ex.GetType() == typeof(UnauthorizedAccessException))
                            {
                                UnauthorizedAccessSomeDirs = true;
                                continue;
                            }
                            throw ex;
                        }
                    }
                    if (appenedDirList.Count > 0)
                        DList.AddRange(appenedDirList);
                }
                while (appenedDirList.Count > 0);
            }

        }

        private void btnCompare_Click(object sender, EventArgs e)
        {
            if (txbPath1.Text.Trim() == "") { MessageBox.Show("Выберете каталог 1"); return; }
            if (txbPath2.Text.Trim() == "") { MessageBox.Show("Выберете каталог 2"); return; }

            bool path1_IsSnapshot = false, path2_IsSnapshot = false;
            if (txbPath1.Text.Length > 3 && txbPath1.Text.Substring(txbPath1.Text.Length - 3).ToLower() == ".sn") path1_IsSnapshot = true;
            if (txbPath2.Text.Length > 3 && txbPath2.Text.Substring(txbPath2.Text.Length - 3).ToLower() == ".sn") path2_IsSnapshot = true;

            if (path1_IsSnapshot && !File.Exists(txbPath1.Text)) { MessageBox.Show(string.Format("Указанного снапшота '{0}' не существует", txbPath1.Text)); return; }
            if (!path1_IsSnapshot && !Directory.Exists(txbPath1.Text)) { MessageBox.Show(string.Format("Указанного каталога '{0}' не существует", txbPath1.Text)); return; }
            if (path2_IsSnapshot && !File.Exists(txbPath2.Text)) { MessageBox.Show(string.Format("Указанного снапшота '{0}' не существует", txbPath2.Text)); return; }
            if (!path2_IsSnapshot && !Directory.Exists(txbPath2.Text)) { MessageBox.Show(string.Format("Указанного каталога '{0}' не существует", txbPath2.Text)); return; }

            EnableUserControls(false);
            progressBar1.Value = 0;
            progressBar1.Visible = true;
            progressBar1.BringToFront();
            label2.Text = "Присутствует только в " + txbPath1.Text;
            label3.Text = "Присутствует только в " + txbPath2.Text;
            Application.DoEvents();

            Only1.Clear();
            Only2.Clear();
            Diff.Clear();

            Snapshot sn1 = new Snapshot(txbPath1.Text, path1_IsSnapshot);
            Snapshot sn2 = new Snapshot(txbPath2.Text, path2_IsSnapshot);
            if (sn1.UnauthorizedAccessSomeDirs || sn2.UnauthorizedAccessSomeDirs) MessageBox.Show("Внимание! По некоторым каталогам информация о седержимом отсутствует, так как не удалось получить доступ");


            List<FileCmp> fList1 = new List<FileCmp>();
            fList1.AddRange(sn1.DList); fList1.AddRange(sn1.FList);
            List<FileCmp> fList2 = new List<FileCmp>();
            fList2.AddRange(sn2.DList); fList2.AddRange(sn2.FList);

            progressBar1.Value = 5;
            Application.DoEvents();
            DateTime dt = DateTime.Now;
            for (int i = 0; i < fList1.Count; i++)
            {
                FileCmp file = fList1[i];
                bool bExistInOther = false;
                foreach (FileCmp file2 in fList2)
                    if (file.LocalPath == file2.LocalPath)
                    {
                        bExistInOther = true;
                        if (!file.IsDirectory)
                        {
                            // сравнение
                            if (file.Len != file2.Len) Diff.Add(file);
                            else
                            {
                                if (chbxCheckMd5.Checked)
                                {
                                    if (file.MD5 == "") file.MD5 = GetMD5(file.Path);
                                    if (file2.MD5 == "") file2.MD5 = GetMD5(file2.Path);
                                    if (file.MD5 != file2.MD5) Diff.Add(file);
                                }
                                else
                                {
                                    if (file.ChangeDate != file2.ChangeDate) Diff.Add(file);
                                }
                            }

                        }
                        break;
                    }

                if (!bExistInOther)
                    Only1.Add(file);
                progressBar1.Value = 5 + (int)(80.0 / (double)fList1.Count * i);
                if (DateTime.Now.Second != dt.Second) { Application.DoEvents(); dt = DateTime.Now; }
            }

            for (int i = 0; i < fList2.Count; i++)
            {
                FileCmp file2 = fList2[i];
                bool bExistInOther = false;
                foreach (FileCmp file1 in fList1)
                    if (file2.LocalPath == file1.LocalPath)
                    {
                        bExistInOther = true;
                        break;
                    }

                if (!bExistInOther)
                    Only2.Add(file2);

                progressBar1.Value = 85 + (int)(15.0 / (double)fList2.Count * i);
                if (DateTime.Now.Second != dt.Second) { Application.DoEvents(); dt = DateTime.Now; }
            }


            txbDiff.Clear();
            txbExistOnly1.Clear();
            txbExistOnly2.Clear();
            foreach (FileCmp file in Diff) txbDiff.AppendText(file.LocalPath + "\r\n");
            foreach (FileCmp file in Only1) txbExistOnly1.AppendText(file.LocalPath + "\r\n");
            foreach (FileCmp file in Only2) txbExistOnly2.AppendText(file.LocalPath + "\r\n");

            progressBar1.Visible = false;
            EnableUserControls(true);
        }

        void EnableUserControls(bool enabled)
        {
            txbPath1.Enabled = enabled;
            btnOpen1.Enabled = enabled;
            txbPath2.Enabled = enabled;
            btnOpen2.Enabled = enabled;
            btnCompare.Enabled = enabled;
            chbxCheckMd5.Enabled = enabled;
            btnSaveSnapshot.Enabled = enabled;
        }

        /// <summary>MD5 из файла </summary>
        /// <param name="fPath">путь к файлу</param>
        /// <param name="AddLen">добавить ли к md5 размер файла</param>
        public static String GetMD5(String fPath)
        {
            byte[] buf = File.ReadAllBytes(fPath);
            byte[] hash = new MD5CryptoServiceProvider().ComputeHash(buf);
            return MD5String(hash);
        }

        // конвертирование в строку
        static string MD5String(byte[] hash)
        {
            StringBuilder sb = new StringBuilder(32);
            foreach (byte hex in hash)
                sb.Append(hex.ToString("X2"));
            return sb.ToString().ToUpper();
        }

        private void btnOpen1_Click(object sender, EventArgs e)
        {
            if (opn.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txbPath1.Text = opn.SelectedPath;
            }
        }

        private void btnOpen2_Click(object sender, EventArgs e)
        {
            if (opn.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txbPath2.Text = opn.SelectedPath;
            }
        }

        private void btnSaveSnapshot_Click(object sender, EventArgs e)
        {
            if (txbPath1.Text.Trim() == "") { MessageBox.Show("Выберете каталог 1"); return; }
            if (!Directory.Exists(txbPath1.Text)) { MessageBox.Show(string.Format("Указанного каталога '{0}' не существует", txbPath1.Text)); return; }

            DirectoryInfo di = new DirectoryInfo(txbPath1.Text);
            dlgSave.DefaultExt = "sn";
            dlgSave.FileName = "Snapshot_" + di.Name.Replace(":\\", "") + "_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm");
            //SnapshotDriveC_2017_05_15_07_25.sn

            if (dlgSave.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;

            EnableUserControls(false);
            Application.DoEvents();
            Snapshot sn = new Snapshot(txbPath1.Text, false);
            if (sn.UnauthorizedAccessSomeDirs) MessageBox.Show("Внимание! По некоторым каталогам информация о седержимом отсутствует, так как не удалось получить доступ");
            sn.Save(dlgSave.FileName);
            EnableUserControls(true);

        }

    }
}
